/*
  Author: xiaokai 
        - email: xiaokai.c@foxmail.com 
        - gitee: https://gitee.com/xiaokaiC
*/ 

#include <lwip/init.h>
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include "dotMap.h"
#include <SPI.h>
#include <Ticker.h>
#include <EEPROM.h>

#define D0 16
#define D1 5
#define D2 4
#define D3 0
#define D4 2
#define D5 14
#define D7 13
#define D8 15

extern "C" {
#include "user_interface.h"
}

#define RowA D3    
#define RowB D2
#define RowC D1
#define RowD D0
#define OE D4
#define R1 D7            
#define CLK D5          
#define LATCH D8      

#define HTML_ADD  0x002C0000
#define SUN_ADD   0x00200000
#define ASCII_ADD 0x002B0000
#define HTML_LEN 5340

#define UNICODE_CN_OFFSET  0x4e00

#define EEPROM_INIT_ADD 0
#define EEPROM_CONF_ADD 2

const char *ssid = "点阵屏";
const char *password = "123456789";

char html[HTML_LEN];

Ticker LatTimer;
Ticker ScrollTimer;

ESP8266WebServer server(80);

IPAddress apIP(192, 168, 0, 1);

uint8_t ram1[PIXEL_HEIGHT][PIXEL_WIDTH / 8] = {0xff};
uint8_t ram2[PIXEL_HEIGHT][PIXEL_WIDTH / 8] = {0xff};

DotMapClass DotMap(ram1, ram2);

typedef enum {
	DM_STOP = 0x00,
	DM_UP,
	DM_DOWN,
	DM_LEFT,
	DM_RIGHT
} DisplayMode_t;

struct displayConfig_s {
	uint8_t        curIndex;
	DisplayMode_t  displayMode;
	uint8_t        fontNum;
	int            fontsIndex[50];
};

displayConfig_s displayConf = {0};


void UTF_8ToUnicode(int *pos, String& str, uint8_t *wnum)
{
	int index = 0;
	uint8_t num = 0;
	int strLeng = str.length();
	while (index < strLeng) {
		if ((str[index] & 0x80 ) == 0) {
			pos[num] = str[index];
			num++;
			index++;
			continue;
		}
		else if ((str[index] & 0xf0) == 0xe0) {
			pos[num] = ((int)(((str[index] & 0x0F) << 4) + ((str[index + 1] >> 2) & 0x0F)) << 8) |  \
			                          (((str[index + 1] & 0x03) << 6) + (str[index + 2] & 0x3F));
			num++;
			index += 3;
			continue;
		}
		else {
			*wnum = 0;
			break;
		}
	}
	*wnum = num;
}

void readyRam(uint8_t (*ram)[PIXEL_WIDTH / 8], uint8_t index) {

	uint8_t buf[4];
	int i, j, n, m;
	uint32_t address;

	for (j = 0; j < PIXEL_WIDTH / 8 / 2; j++)
	{
		if (index + j < displayConf.fontNum) {

			Serial.println(index + j);
			Serial.println(displayConf.fontsIndex[index + j]);
			if (displayConf.fontsIndex[index + j] < 225)
			{
				address = ASCII_ADD + (displayConf.fontsIndex[index + j]) * 32;

			} else {
				address = SUN_ADD + (displayConf.fontsIndex[index + j] - UNICODE_CN_OFFSET) * 32;
			}

			for (i = 0; i < PIXEL_HEIGHT / 2; i++) {
				spi_flash_read(address + 4 * i, (uint32 *)&buf, 4);
				for (n = 0; n < 2; n++)
				{
					for (m = 0; m < 2; m++)
					{
					ram[i * 2 + n][j * 2 + m] = buf[n * 2 + m];
					}
				}
			}
		}
  }
}


void clearRam(uint8_t (*ram)[PIXEL_WIDTH / 8]) {
	for (int i = 0; i < PIXEL_HEIGHT; i++) {
		for (int j = 0; j < PIXEL_WIDTH / 8; j++) {
		ram[i][j] = 0xff;
		}
	}
}


void refreshRam(uint8_t (*ram1)[PIXEL_WIDTH / 8], uint8_t (*ram2)[PIXEL_WIDTH / 8]) {
	
	displayConf.curIndex += 2;
  
	if( displayConf.fontNum <= displayConf.curIndex){
		displayConf.curIndex = 0;	
	}
	
	clearRam(ram2); 
	readyRam(ram2, displayConf.curIndex);

}


void printRam(uint8_t (*ram)[PIXEL_WIDTH / 8]) {
	
	for (int i = 0; i < PIXEL_HEIGHT; i++) {
		for (int j = 0; j < PIXEL_WIDTH / 8; j++) {
			Serial.print(ram[i][j], HEX);
			Serial.print("-");
		}
		Serial.println();
	}
}

void redisplay(){
    clearRam(ram1);
    readyRam(ram1, 0);
    refreshRam(ram1, ram2);
}


/* Go to http://192.168.0.1 in a web browser to see current lease */
void handleRoot() {
  server.send(200, "text/html", html);
}

void handleSet() {
	
	if (server.hasArg("dir")) {
		String dir = server.arg("dir");
		if (dir == "up")
		{
			displayConf.displayMode = DM_UP;
		} else if (dir == "down") {
			displayConf.displayMode = DM_DOWN;
		} else if (dir == "left") {
			displayConf.displayMode = DM_LEFT;
		} else if (dir == "right") {
			displayConf.displayMode = DM_RIGHT;
		} else if (dir == "stop") {
			displayConf.displayMode = DM_STOP;
		}
	}

	if (server.hasArg("txt")) {
		String text = server.arg("txt");
		UTF_8ToUnicode(displayConf.fontsIndex, text, &displayConf.fontNum);
		displayConf.displayMode = DM_STOP;  
	}
	
	DotMap.cleanOffset();
	displayConf.curIndex = 0;
	redisplay();
  
	byte *g = (byte *)&displayConf;
	for (int i = 0; i < sizeof(displayConf); i++) {
		EEPROM.write(EEPROM_CONF_ADD + i, *(g + i));
	}
	EEPROM.commit();
	server.send(200, "text/plain","更新成功！");
}


void rowSacn(byte r) { 
	digitalWrite(RowA, (r & 0x01));
	digitalWrite(RowB, (r & 0x02));
	digitalWrite(RowC, (r & 0x04));
	digitalWrite(RowD, (r & 0x08));
}

void screenScan(uint8_t (*cache)[PIXEL_WIDTH / 8]) {
	byte row;
	uint32_t col;
	
	for (row = 0; row < PIXEL_HEIGHT; row++) {
		for (col = 0; col < PIXEL_WIDTH / 8; col++) {
			SPI.transfer(cache[row][col]);
		}
		digitalWrite(OE, 1); 
		rowSacn(row);           
		digitalWrite(LATCH, 0);    
		digitalWrite(LATCH, 1);
		digitalWrite(OE, 0); 
	}
	
	os_delay_us(46);
	digitalWrite(OE, 1);
}

void refreshDisplay() {
	screenScan(ram1);
}

void scrollRefresh() {
	
	switch (displayConf.displayMode) {
		case DM_UP: DotMap.scrollCacheUp(); break;
		case DM_DOWN: DotMap.scrollCacheDown(); break;
		case DM_LEFT: DotMap.scrollCacheLeft(); break;
		case DM_RIGHT: DotMap.scrollCacheRight(); break;
	}
	
	if (displayConf.displayMode != DM_STOP) {
		if ( DotMap.isPrepCacheUpdate() == true) {
			refreshRam(ram1, ram2);
		}
	}
}

void setup() {
	
	EEPROM.begin(50);
	
	if (EEPROM.read(EEPROM_INIT_ADD) != 0x39 && EEPROM.read(EEPROM_INIT_ADD + 1) != 0x93) {
		for (int i = 2; i < 50; i++) {
			EEPROM.write(i, 0);
		}
		EEPROM.write(EEPROM_INIT_ADD, 0x39);
		EEPROM.write(EEPROM_INIT_ADD + 1, 0x93);
		EEPROM.commit();
	}
	
	/*Read config from EEPROM*/
	for (int i = 0; i < sizeof(displayConf); i++) {
		byte *s = (byte *)&displayConf;
		*(s + i) = EEPROM.read(EEPROM_CONF_ADD + i);
	}
	
	/* List of mac address for static lease */
	uint8 mac_CAM[6] = { 0x00, 0x0C, 0x43, 0x01, 0x60, 0x15 };
	uint8 mac_PC[6] = { 0xb4, 0x52, 0x7e, 0x9a, 0x19, 0xa5 };
  
	/*GPIO init*/
	pinMode(RowA, OUTPUT);
	pinMode(RowB, OUTPUT);
	pinMode(RowC, OUTPUT);
	pinMode(RowD, OUTPUT);
	pinMode(OE, OUTPUT); 

	pinMode(R1, OUTPUT);
	pinMode(CLK, OUTPUT); 
	pinMode(LATCH, OUTPUT); 
	Serial.begin(115200);
  
	/*Hardware SPI init*/
	SPI.begin();
	SPI.setBitOrder(MSBFIRST);
  
	/*Timer init*/
	LatTimer.attach_ms(20, refreshDisplay);
	ScrollTimer.attach(0.2, scrollRefresh);
  
	Serial.println();
	Serial.println("Configuring access point...");
	spi_flash_read(HTML_ADD, (uint32 *)html, HTML_LEN);
	/* Disable the WiFi persistence to avoid any re-configuration that may erase static lease when starting softAP */
	WiFi.persistent(false);

	WiFi.mode(WIFI_AP);
	/* Configure AP with IP = 192.168.0.1 / Gateway = 192.168.0.1 / Subnet = 255.255.255.0*/
	WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
	wifi_softap_add_dhcps_lease(mac_CAM);  // always 192.168.0.100
	wifi_softap_add_dhcps_lease(mac_PC);   // always 192.168.0.101
  
  
	/* Start Access Point. You can remove the password parameter if you want the AP to be open. */
	WiFi.softAP(ssid, password);
  
	/* Setup HTTP Server */
	server.on("/", handleRoot);
	server.on("/set", handleSet);
	server.begin();
	Serial.println("HTTP server started");

	/*refresh display*/
	if (displayConf.fontNum > 0)
	{
		clearRam(ram1);
		readyRam(ram1, displayConf.curIndex);
		refreshRam(ram1, ram2);
	}
}

void loop() {
	server.handleClient();
}
